/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.tchepannou.rails.engine.container;

import com.tchepannou.rails.core.annotation.Job;
import com.tchepannou.rails.core.api.ContainerContext;
import com.tchepannou.rails.core.api.JobController;
import com.tchepannou.rails.core.exception.InitializationException;
import com.tchepannou.rails.core.exception.JobException;
import com.tchepannou.rails.core.service.JobService;
import com.tchepannou.rails.core.service.JobService.JobExecutor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Default implementation of {@link ActionControllerProvider}.
 *
 * @author herve
 */
public class JobControllerContainer
{
    //-- Static Attribute
    private static final Logger LOG = LoggerFactory.getLogger (JobControllerContainer.class);
    
    //-- Attribute
    private ContainerContext _containerContext;
    private List<JobService.JobInfo> _jobs = new ArrayList<JobService.JobInfo> ();
    private boolean _initialized;

    //-- Constructeur
    public JobControllerContainer ()
    {
    }

    //-- Public method
    public void init (ContainerContext containerContext)
    {
        if ( LOG.isTraceEnabled () )
        {
            LOG.trace ("init(" + containerContext + ")");
        }
        LOG.info ("Initializing");
        _containerContext = containerContext;
        try
        {
            loadJobControllerWrappers (_containerContext.getBasePackage () + ".job");
            initJobs ();
            _initialized = true;

            LOG.info ("Initialized");
        }
        catch ( ClassNotFoundException e )
        {
            throw new InitializationException ("Unable to initialized the ActionControllerContainer", e);
        }
        catch ( IOException e )
        {
            throw new InitializationException ("Unable to initialized the ActionControllerContainer", e);
        }
        catch ( JobException e )
        {
            throw new InitializationException ("Unable to initialized the ActionControllerContainer", e);
        }
    }

    public void destroy ()
    {
        if ( LOG.isTraceEnabled () )
        {
            LOG.trace ("destroy()");
        }

        LOG.info ("Destroying");
        _jobs.clear ();
        _initialized = false;
        LOG.info ("Destroyed");
    }

    public boolean isInitialized ()
    {
        return _initialized;
    }

    public List<JobService.JobInfo> getJobs ()
    {
        return _jobs;
    }

    //-- Private
    private void loadJobControllerWrappers (String packageName)
        throws ClassNotFoundException,
               IOException
    {
        /* Package directory */
        ClassLoader classLoader = Thread.currentThread ().getContextClassLoader ();
        assert classLoader != null;
        String path = packageName.replace ('.', '/');
        Enumeration<URL> resources = classLoader.getResources (path);
        List<File> dirs = new ArrayList<File> ();
        while ( resources.hasMoreElements () )
        {
            URL resource = resources.nextElement ();
            dirs.add (new File (resource.getFile ()));
        }

        /* Load the classes */
        for ( File directory: dirs )
        {
            loadClasses (directory, packageName);
        }
    }

    /**
     * Recursive method used to find all classes in a given directory and subdirs.
     *
     * @param directory   The base directory
     * @param packageName The package name for classes found inside the base directory
     * @return The classes
     * @throws ClassNotFoundException
     */
    private void loadClasses (File directory, String packageName)
        throws ClassNotFoundException
    {
        if ( !directory.exists () )
        {
            return;
        }
        File[] files = directory.listFiles ();
        for ( File file: files )
        {
            String name = file.getName ();
            if ( name.endsWith (JobController.CLASSNAME_SUFFIX + ".class") )
            {
                Class clazz = Class.forName (packageName + '.' + file.getName ().substring (0, file.getName ().length () - 6));
                int modifier = clazz.getModifiers ();
                if ( Modifier.isPublic (modifier)
                    && !Modifier.isAbstract (modifier)
                    && !Modifier.isInterface (modifier)
                    && JobController.class.isAssignableFrom (clazz) )
                {
                    registerJobController (clazz);
                }
            }
        }
    }

    private void registerJobController (Class<? extends JobController> clazz)
    {
        for ( Method method: clazz.getMethods () )
        {
            if ( method.getAnnotation (Job.class) != null )
            {
                int modifier = method.getModifiers ();
                Object[] params = method.getTypeParameters ();
                if ( Modifier.isPublic (modifier)
                    && !Modifier.isAbstract (modifier)
                    && JobController.class.isAssignableFrom (clazz)
                    && (params == null || params.length == 0) )
                {
                    registerJob (clazz, method);
                }
            }
        }
//        ActionControllerWrapper wrapper = new ActionControllerWrapper ();
//        wrapper.setControllerClass (clazz);
//
//        Action action = (Action)clazz.getAnnotation (Action.class);
//        if (action != null)
//        {
//            Class<? extends ActiveRecord> model = action.modelClass ();
//            if (model != Action.ActiveRecordDefault.class)
//            {
//                wrapper.setModelClass (model);
//            }
//        }
//
//        if (LOG.isDebugEnabled ())
//        {
//            LOG.debug ("Registering ActionController: /" + wrapper.getName () + "=" + clazz);
//        }
//        _wrappers.add (wrapper);
    }

    private void registerJob (final Class<? extends JobController> clazz, final Method method)
    {
        Job job = method.getAnnotation (Job.class);
        if ( job == null )
        {
            return;
        }
        final String expr = job.cron ();
        if ( LOG.isDebugEnabled () )
        {
            LOG.debug ("New Job: " + method + " @ " + expr);
        }

        JobService.JobInfo info = new JobService.JobInfo ()
        {
            public String getCronExpression ()
            {
                return expr;
            }

            public Class<? extends JobController> getControllerClass ()
            {
                return clazz;
            }

            public Method getMethod ()
            {
                return method;
            }

            public ContainerContext getContainerContext ()
            {
                return _containerContext;
            }

            public JobExecutor getExecutor ()
            {
                return new JobControllerWrapper ();
            }
        };
        _jobs.add (info);
    }

    private void initJobs ()
        throws JobException
    {
        JobService js = ( JobService ) _containerContext.findService (JobService.class);
        if (js != null)
        {
            for ( JobService.JobInfo job: _jobs )
            {
                js.schedule (job);
            }
        }
        else
        {
            LOG.warn ("No JobService available. none of the job will be schedules");
        }
    }
}
